
import crypto from 'crypto'

// nodejs8没有base64url这种encoding，此处为对应的polyfill

function toBase64Url (str) {
  return str.replace(/=/g, '')
    .replace(/\+/g, '-')
    .replace(/\//g, '_')
}

function fromBase64Url (base64url) {
  base64url = base64url.toString()

  var padding = 4 - base64url.length % 4
  if (padding !== 4) {
    for (var i = 0; i < padding; ++i) {
      base64url += '='
    }
  }
  return base64url
    .replace(/-/g, '+')
    .replace(/_/g, '/')
}

function atob (str) {
  return Buffer.from(str, 'base64').toString('utf-8')
}

function btoa (str) {
  return Buffer.from(str, 'utf-8').toString('base64')
}

function fromToken (base64url) {
  return JSON.parse(
    atob(
      fromBase64Url(
        base64url
      )
    )
  )
}

function toToken (obj) {
  return toBase64Url(
    btoa(
      JSON.stringify(
        obj
      )
    )
  )
}

function getSign (str, secret) {
  return toBase64Url(
    crypto.createHmac('sha256', secret).update(str).digest('base64')
  )
}

const jwt = {
  verify: function (token, secret) {
    if (typeof token !== 'string') {
      throw new Error('Invalid token')
    }
    const tokenArr = token.split('.')
    if (tokenArr.length !== 3) {
      throw new Error('Invalid token')
    }
    const [headerBase64, payloadBase64, signatureBase64] = tokenArr
    if (getSign(headerBase64 + '.' + payloadBase64, secret) !== signatureBase64) {
      throw new Error('Invalid token')
    }
    const header = fromToken(headerBase64)
    if (header.alg !== 'HS256' || header.typ !== 'JWT') {
      throw new Error('Invalid token')
    }
    const payload = fromToken(payloadBase64)

    if (payload.exp * 1000 < Date.now()) {
      const err = new Error('Token expired')
      err.name = 'TokenExpiredError'
      throw err
    }
    return payload
  },
  sign: function (data, secret, options = {}) {
    const {
      expiresIn
    } = options
    if (!expiresIn) {
      throw new Error('expiresIn is required')
    }
    const header = {
      alg: 'HS256',
      typ: 'JWT'
    }
    const now = parseInt(Date.now() / 1000)
    const payload = {
      ...data,
      iat: now,
      exp: now + options.expiresIn
    }
    const signStr = toToken(header) + '.' + toToken(payload)
    const signature = getSign(signStr, secret)
    return signStr + '.' + signature
  }
}

export default jwt
